import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.regex.PatternSyntaxException;
import org.htmlparser.Node;
import org.htmlparser.Tag;
import org.htmlparser.lexer.Page;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.tags.ScriptTag;
import org.htmlparser.tags.StyleTag;

/**
 * @author ofer rotberg
 *
 */
public class ScriptNode implements Serializable {

	private static final long serialVersionUID = 1L;
	public static String newline = System.getProperty("line.separator");
	private static String singleLineComment = "//.*";
	private static String multiLineComment = "/\\*[.\\r\\n]*\\*/";
	public enum SourceType { EXTERNAL, INLINE }
	public enum ScriptType { TAG, URI_SCHEME, ATTR, EVENT, UNKNOWN }

	private Node node;
	private Tag tag;
	private SourceType sourceType = SourceType.INLINE;
	private ScriptType scriptType = ScriptType.UNKNOWN;
	private String externalSourceAddress=null;
	private String scriptPath = null; // scriptPath lists the nodes names from this node to root
	private int hash=0, sourceHash = 0, alternateHash=0;
	private boolean skip = false;
	
	public void setAlternateHash(int alternateHash) {
		this.alternateHash = alternateHash;
	}

	private String attr= null, event=null;
	private String origCode=null,normCode=null;

	public void setNormCode(String normCode) {
		this.normCode = normCode;
		this.alternateHash = this.normCode.hashCode();
	}

	public boolean IsExternal(){
		if (this.scriptType.equals(SourceType.EXTERNAL))
			return true;
		else
			return false;
	}
	
	public String getOrigCode() {
		return origCode;
	}

	public String getNormCode() {
		return normCode;
	}

	private int findToken(int index){
		int nl = origCode.indexOf("\n",index);
		int col = origCode.indexOf(";",index);
		int next=0;
		if ( (nl>-1) && (col>-1) )
			next = Math.min(nl, col);
		else if ( (nl<0) && (col<0))
			return 0;
		else 
			next = Math.max(nl, col);
		String currLine  = this.origCode.substring(index,next);
		String[] vars = currLine.split(",");
		if (vars.length>1){
			next =currLine.indexOf(vars[1]);
			return next;
		}else
			return 0;


	}

	private int FindNextToken(int index){
		int[] eol = new int[10];
		eol[0] = origCode.indexOf(";", index);
		//eol[1] = origCode.indexOf("\r", index);
		//eol[2] = origCode.indexOf("\n", index);
		eol[3] = origCode.length();
		eol[4] = origCode.indexOf(",",index);
		eol[5] = origCode.indexOf("=",index);
		eol[6] = origCode.indexOf("(",index);
		eol[7] = origCode.indexOf("[",index);
		eol[8] = origCode.indexOf("{",index);
		Arrays.sort(eol);
		int t=0;
		while (eol[t]<=0)
			t+=1;
		/*		char ch = origCode.charAt(eol[t]);
		if ( (ch=='(') || (ch=='[') || (ch=='{') ){
			//we have to deal with complex context like new String(new A(),new B() )
			int cnt = 1,currIndex=eol[t];
			char nextCh;
			while (cnt>0){
				currIndex+=1;
				nextCh = origCode.charAt(currIndex);
				if ((nextCh=='(') || (nextCh=='[') || (nextCh=='{') )
					cnt+=1;
				if ((nextCh==')') || (nextCh==']') || (nextCh=='}') )
					cnt-=1;
			}
			return(currIndex+1);
		}
		else*/
		return(eol[t]);
	}

	private static String FindIdentifier(String origCode,int i) throws DetectorException{
		int loc=0,j=0;
		//declarations types - var A=B; , var A; , var A , var A in B , var A=x,B=y,C=z
		int[] temp = new int[10];
		if (origCode.charAt(i)!=',')
			i=i+4;
		else{
			i+=1;
			while (origCode.charAt(i)==' ')
				i+=1;
		}
		//search identifier declaration type
		temp[0] = origCode.indexOf(";", i);
		temp[1] = origCode.indexOf("=", i);
		temp[2] = origCode.indexOf(" in ", i);
		temp[3] = origCode.length();
		temp[4] = origCode.indexOf(",",i);
		temp[5] = origCode.indexOf(" ",i);
		//find the minimum value that is different from -1
		Arrays.sort(temp);
		while(temp[j]<=0){
			j=j+1;
		}
		if (j<=temp.length){
			loc=temp[j];
			/*if (origCode.charAt(loc)==',')
				return (origCode.substring(i, loc).trim());
			else*/
			return (origCode.substring(i, loc).trim());
		}
		else 
			throw (new DetectorException("Problem with variables normalization. String = " + origCode));

	}

	private  static String RemoveComments(String origCode){
		StringBuilder sb = new StringBuilder(origCode);
		//remove multi-line comments
		int multiLineCommentStart = sb.indexOf("/*"),multiLineCommentEnd=0;
		while(multiLineCommentStart>-1){
			multiLineCommentEnd = sb.indexOf("*/",multiLineCommentStart);
			if (multiLineCommentEnd>-1) 
				sb.replace(multiLineCommentStart, multiLineCommentEnd+2, "MULL_COMM");
			else{
				multiLineCommentStart = sb.indexOf("/*",multiLineCommentStart+2);
				continue;
			}
			multiLineCommentEnd = sb.lastIndexOf("MULL_COMM");
			multiLineCommentStart = sb.indexOf("/*",multiLineCommentEnd);
		}
		origCode = sb.toString();
		//remove single-line comments
		origCode = origCode.replaceAll(singleLineComment,"SIN_COMM");
		return (origCode);
		
	}

	/**
	 * normalize method is used to turn the script code to canonical form by replacing:
	 * quotes with constant STR
	 * numbers with constant INT
	 * arrays initialization with constant ARRAY
	 * special (meta) characters with their corresponding character
	 * identifiers names to constant IDEN
	 * @throws DetectorException
	 */
	public static String normalize(String origCode) throws DetectorException{
		//some method variables
		String identifier = null;
		String identifierValue = null;
		String normCode = null;
		int idenEnd = 0, idenStart=0, idenCount=0;

		//remove comments
		origCode = RemoveComments(origCode);
		try{
			//remove \r and \n and \t from original script
			origCode = origCode.replaceAll("[\\r\\n]+","\r\n");
			//origCode = origCode.replaceAll("\\n", " ");
			origCode = origCode.replaceAll("\\t", " ");
			normCode = origCode.toLowerCase();

			//Currently I'm ignoring long script because the replaceAll method is having troubles with it.
			/*if (origCode.length()>100000)
			normCode=origCode; 
			 */
			//replace special characters
			normCode = normCode.replaceAll("&amp;","&");			
			normCode = normCode.replaceAll("&lt;","<");			
			normCode = normCode.replaceAll("&gt;",">");			
			normCode = normCode.replaceAll("&#039;","'");		
			normCode = normCode.replaceAll("&quot;","\"");

			//replace \' and \" with white space to clearly identify strings
			normCode = normCode.replaceAll("\\\\'","");
			normCode = normCode.replaceAll("\\Q\\\"\\E","");

			/*cut off white spaces from expression ..=new... this would help us to detect identifiers that 
		are declared using constructors*/ 
			normCode = normCode.replaceAll("\\s+=\\s+new ", "=new ");

			/*replace identifiers with constant IDEN.
		identifiers will be found using original code because normalized code will
		change during run. */

			//find next index of variable declaration
			int i = origCode.indexOf("var ");
			while (i>-1){
				identifier = FindIdentifier(origCode,i);
				//System.out.println(identifier);
				//skip weird identifier names
				if (identifier.startsWith("~")){
					i = origCode.indexOf("var ",i+4);
					continue;
				}
				/*	//find identifier's value (if it is an assignment)
				int j = origCode.indexOf(identifier, i);
				j=j+identifier.length();
				int nt = FindNextToken(j), nnt=0;
				if (origCode.charAt(nt)=='='){//regular assignment
					nnt = FindNextToken(nt+1);
					identifierValue=origCode.substring(nt+1,nnt).toLowerCase().trim();
					nt=nnt;
				}
				 */
				//currently the detector can't trace multi-variables declaration (var a=0, b="STRING", c={..}
				/*
				//compute next variable index, before string changes
				int j = origCode.indexOf(identifier, i+3);
				//j=j+identifier.length();
				//int nt = FindNextToken(j);
				int nt = findToken(j);
				//multiple variables declaration
				if (nt>0)
					i = nt;
				else{
					i = origCode.indexOf("var ",i+4);
					//deal with cases that the string var is in double quotes
					while ((i>1) && (origCode.charAt(i-1)=='\"'))
						i = origCode.indexOf("var ",i+4);
				}*/


				//check if identifier is an array declaration. If so, change identifier's value to const ARR
				//Here we check the declaration form myArray=[0,1,1..];
				int nt = origCode.indexOf(identifier,i+3) + identifier.length();
				String curr=null;;
				if (origCode.indexOf(";",nt)>-1)
					curr = origCode.substring(nt,origCode.indexOf(";",nt)).toLowerCase().trim();
				else{
					System.out.println("Problematic script...Skipping");
					i = origCode.indexOf("var",nt);
					continue;
				}
				String decType = "null";
				int tmp = 0;
				if (curr.startsWith("=")){
					nt = origCode.indexOf("=",nt);
					tmp = curr.indexOf('=');
					curr = curr.substring(tmp+1).trim();
					if (curr.startsWith("new")){
						tmp = curr.indexOf("new") + 3;
						curr = curr.substring(tmp).trim();
						if (curr.startsWith("array")){
							decType = "arrNew";
							nt = origCode.indexOf("Array",nt)+5;
							while (origCode.charAt(nt)==' ')
								nt+=1;
							if (origCode.charAt(nt)!='('){
								//throw new DetectorException("Problematic script");
								System.out.println("Problematic Script...Continue");
								i = origCode.indexOf("var ",nt);
								continue;
							}

						}
					}
					else if (curr.charAt(0)=='['){
						decType="arr";
						nt = origCode.indexOf("[",nt);
					}
					else if (curr.charAt(0)=='{'){
						decType="obj";
						nt = origCode.indexOf("{",nt);
					}
				}

				//check if identifier is an array declaration.of the form var f=[1,2,3]. If so, change identifier's value to const OBJ
				if (decType.equals("arr")){
					int cnt=1,nnt=nt;
					while (cnt!=0){
						nnt+=1;
						if (origCode.charAt(nnt)==']')
							cnt-=1;
						else if (origCode.charAt(nnt)=='[')
							cnt+=1;
					}
					String arrValue = origCode.substring(nt+1,nnt).toLowerCase();
					if (arrValue.length()>0) 
						normCode = normCode.replaceAll("\\Q" + arrValue + "\\E","ARR");
					i = origCode.indexOf("var ",nnt);
				}//check if identifier is an object declaration. If so, change identifier's value to const OBJ
				else if (decType.equals("obj")){
					int cnt=1,nnt=nt;
					while (cnt!=0){
						nnt+=1;
						if (origCode.length()==nnt)
							throw new DetectorException("JS Problem");
						if (origCode.charAt(nnt)=='}')
							cnt-=1;
						else if (origCode.charAt(nnt)=='{')
							cnt+=1;
					}
					String objValue = origCode.substring(nt+1,nnt).toLowerCase();
					int h = normCode.indexOf(objValue);
					if (objValue.length()>0) 
						normCode = normCode.replaceAll("\\Q" + objValue + "\\E","OBJ");
					i = origCode.indexOf("var ",nnt);
				}//check if identifier is an Array Declaration of the form var f=new Array(45)
				else if (decType.equals("arrNew")){
					int cnt=1,nnt=nt;
					while (cnt!=0){
						nnt+=1;
						if (origCode.length()==nnt)
							throw new DetectorException("JS Problem");
						if (origCode.charAt(nnt)=='(')
							cnt+=1;
						else if (origCode.charAt(nnt)==')')
							cnt-=1;
					}
					String arrValue = origCode.substring(nt+1,nnt).toLowerCase();
					if (arrValue.length()>0) 
						normCode = normCode.replaceFirst(arrValue,"ARR");
					i = origCode.indexOf("var ",nnt);
				}
				else
					i = origCode.indexOf("var ",i+4);

				//replace the exact identifier (and not the identifier if it is included in some other string)

				idenCount+=1;

				normCode = normCode.replaceAll("\\b" + identifier.toLowerCase() + "\\b",idenCount +"_IDEN");

			}

			/*			//find identifiers that are declared without the keyword "var" but as a returned value from object constructor.
			//for example: iden_example = new Object(...);

			i = normCode.indexOf("new ");
			while (i>-1){
				int j = i-1;
				//check if it is indeed an assignment operation
				if (normCode.charAt(j) == '='){
					idenEnd = j;
					boolean b = false;
					while      (!b) {
						b=normCode.substring(j-1, j).equals(" ") || normCode.substring(j-1, j).equals("\r") 
						|| normCode.substring(j-1, j).equals("\t") || normCode.substring(j-1, j).equals("\n") || 
						normCode.substring(j-1,j).equals("{") || normCode.substring(j-1,j).equals(";") 
						|| normCode.substring(j-1,j).equals("(") || normCode.substring(j-1,j).equals(",") 
						|| normCode.substring(j-1,j).equals(")");
						j-=1;
					}
					idenStart = j+1;
					identifier = normCode.substring(idenStart,idenEnd);
					//System.out.println(identifier);
					if (!( (identifier.indexOf("IDEN") > -1) || (identifier.indexOf("ARR") > -1)
							|| (identifier.indexOf("INT")>-1) || (identifier.indexOf("~")>-1) ) ) {
						idenCount+=1;
						normCode.replaceAll("\\b" + identifier + "\\b",idenCount +"_IDEN");
					}
				}
				//advance to the next appearance of the keyword "new" 
				i = normCode.indexOf("new ",i+3);
			}
			 */

			//replace all strings inside double quotes
			normCode = normCode.replaceAll("[\"][^\"]*[\"]", "STR");
			//replace all strings inside single quotes
			normCode = normCode.replaceAll("['][^']*[']", "STR");
			//replace all integers and floats with constant NUM
			normCode = normCode.replaceAll("[+-]?\\d+[\\.]{0,1}\\d*[^_]", "NUM");

		}catch (PatternSyntaxException e){
			System.out.println(e.getMessage());
		}

		normCode = normCode.trim();
		return(normCode);
	}

	/**
	 * 
	 * @throws DetectorException
	 * @throws IOException
	 */
	private void setScriptCode() throws DetectorException, IOException {
		if (this.sourceType == SourceType.EXTERNAL) {
			//read JavaScript source from URL 
			//first, we have to check if this url really points to JavaScript code
			URL url;
			BufferedReader br = null;
			try {
				url = new URL(this.externalSourceAddress);
				URLConnection uc = url.openConnection();
				br = new BufferedReader(new InputStreamReader(uc.getInputStream()));
				String input = br.readLine();
				StringBuffer sb = new StringBuffer();
				while (input != null){
					sb.append(input);
					sb.append(newline);
					input = br.readLine();
				}
				this.origCode = sb.toString();

			} catch (IOException e) {
				System.out.println(externalSourceAddress + "is problemtic. Skipping...");
			}
		}
		else //this is an INLINE script
			if (tag instanceof ScriptTag)
				this.origCode = ((ScriptTag)tag).getScriptCode();
			else if (tag instanceof StyleTag)
				this.origCode = ((StyleTag)tag).getStringText();
	} 

	/**
	 * setSourceURL method is used to set the correct and full URL of the script source (assuming this is an EXTERNAL script)   
	 * @param scriptSource
	 * @throws DetectorException
	 */
	private void setSourceURL(String scriptSource) throws DetectorException  {
		this.sourceType = SourceType.EXTERNAL;
		this.externalSourceAddress = scriptSource.trim();

		//sometimes the URL includes special characters 
		this.externalSourceAddress = this.externalSourceAddress.replaceAll("&amp;","&");
		int ascii_value = (int)this.externalSourceAddress.charAt(0);

		//check if source is an absolute URL
		if (this.externalSourceAddress.startsWith("http") || this.externalSourceAddress.startsWith("ftp") )
			return; // no need to manipulate this string

		//sometimes the source is not absolute (e.g. : /javascripts/... or ../../javascrips/... or simply start without / like js/... )
		else if (this.externalSourceAddress.startsWith("/")  || (this.externalSourceAddress.startsWith("."))){
			Page page = tag.getPage();
			URL url;
			try {
				if (page.getUrl()!=null)
					url = new URL(page.getUrl());
				else
					url = new URL("http://ha.ckers.org/xss.js");
			} catch (MalformedURLException e) {
				throw (new DetectorException(e.getMessage()));
			}
			if (this.externalSourceAddress.startsWith(".")){
				int j = this.externalSourceAddress.lastIndexOf("../");
				this.externalSourceAddress = this.externalSourceAddress.substring(j+2);
			}
			this.externalSourceAddress = url.getProtocol() + "://" + url.getHost() + this.externalSourceAddress;
			return;
		}
		//if source starts with a character (like js/..). Check if source starts with an English letter
		else if ((ascii_value>64 && ascii_value<91) || (ascii_value>60 && ascii_value<123)){
			Page page = tag.getPage();
			URL url;
			try {
				url = new URL(page.getUrl());
			} catch (MalformedURLException e) {
				throw (new DetectorException(e.getMessage()));
			}
			int p = url.getPath().lastIndexOf('/');
			this.externalSourceAddress = url.getProtocol() + "://" + url.getHost() + url.getPath().substring(0, p+1) + this.externalSourceAddress;
			return;
		}
		else{
			System.out.println("This is an external source, but there is an URL Error." + newline + 
					"URL is " + this.externalSourceAddress);
		}
	}

	/**
	 * handleScriptTag is called when script extractor identifies a script inside a tag (usually, <SCRIPT>...</SCRIPT>)
	 * @throws DetectorException
	 * @throws IOException
	 */
	private void handleScriptTag() throws DetectorException, IOException {

		this.scriptType = ScriptNode.ScriptType.TAG;
		// check is this is an external script. Get value of "src" attribute
		String scriptSource = tag.getAttribute("src");
		if (scriptSource != null) 	//this is am external script
			setSourceURL(scriptSource);
		else				//this is an internal script
			this.sourceType = SourceType.INLINE;
		setScriptCode();
	}

	/**
	 * handleURIScheme method is called when script extractor identifies a script that is called using URI scheme (javascript:)
	 * @throws DetectorException
	 */
	private void handleURIScheme() throws DetectorException{

		this.scriptType = ScriptNode.ScriptType.URI_SCHEME;
		//search for the JavaScript: starting point
		int start = origCode.indexOf("javascript");
		start = origCode.indexOf(":",start);
		if (start>-1)
			origCode = origCode.substring(start+1);
		else
			throw (new DetectorException("Problem with URI scheme item: " + origCode));
	}

	/*
	 * handleAttr method sets the script code from the attribute's value

	private void handleAttr() throws DetectorException{
		this.origCode = tag.getAttribute(attr);
		//first, check if attribute contains URI scheme (javascript:...)
		if (origCode.toLowerCase().indexOf("javascript")>-1){
			this.scriptType = ScriptType.URI_SCHEME;
			int start = origCode.toLowerCase().indexOf("javascript");
			this.origCode = this.origCode.substring(start);
		}
	}
	 */

	/*
	 * handleEvent method sets the script code from the event's value

	private void handleEvent() throws DetectorException{
		this.origCode = tag.getAttribute(event);
		//this.alternativeScriptCode = normalize(this.scriptCode);
	}
	 */

	//this constructor is for test purposes only
	public ScriptNode(String jsCode) throws DetectorException{
		this.origCode = jsCode;
		//this.normCode = normalize(origCode);
	}

	public void HandleExpression(){
		int start = origCode.indexOf("expression");
		origCode = origCode.substring((start+10),origCode.length());
	}
	
	/**
	 * Main constructor
	 * @param tag
	 * @param mode
	 * @throws DetectorException
	 * @throws IOException
	 */
	public ScriptNode(Tag tag, ScriptsDB2.AppMode mode) throws DetectorException, IOException{

		if (tag instanceof ScriptTag) {
			this.tag = (ScriptTag)tag;
			handleScriptTag();
		}else if (tag instanceof StyleTag){
			this.tag = (StyleTag)tag;
			this.origCode = ((StyleTag)tag).getStringText().toLowerCase();
			if (origCode.matches(".*javascript[\\s]*:.*"))
				handleURIScheme();
		}else if (tag instanceof AttrTag){
			this.tag = (AttrTag)tag;
			this.scriptType = ScriptNode.ScriptType.ATTR;
			this.origCode  = ((AttrTag)tag).getValue().toLowerCase();
			if (origCode.matches(".*j[\\s]*a[\\s]*v[\\s]*a[\\s]*s[\\s]*c[\\s]*r[\\s]*i[\\s]*p[\\s]*t[\\s]*:.*"))
				handleURIScheme();
			if (origCode.matches(".*e[\\s]*x[\\s]*p[\\s]*r[\\s]*e[\\s]*s[\\s]*s[\\s]*i[\\s]*o[\\s]*n[\\s]*\\(.*"))
				HandleExpression();
		}else if (tag instanceof EventTag){
			this.tag = (EventTag)tag;
			this.scriptType = ScriptNode.ScriptType.EVENT;
			this.origCode  = ((EventTag)tag).getValue().toLowerCase();
			if (origCode.matches(".*javascript[\\s]*:.*"))
				handleURIScheme();
		}else
			throw(new DetectorException("Unrecognized Tag type " + tag.toString()));
		//check if tag holds any code
		if (origCode == null){
			System.out.println("this tag is empty...continue");
			return;
		}
		//normalize only in learning mode
		if  (mode == ScriptsDB2.AppMode.LEARN){
			//normalize();
			ScriptNode sn = new Normalizer(this).Normalize();
			this.normCode = sn.getNormCode();
			this.skip = sn.isSkip();
		}
		else if ((mode == ScriptsDB2.AppMode.DETECT))
			normCode = origCode;
		else
			throw (new DetectorException("Detection mode not recognized: " + mode));
		hash = origCode.hashCode();
		alternateHash = normCode.hashCode();
	}

	/**
	 * Old constructor
	 * @param node
	 * @param str
	 * @throws DetectorException
	 * @throws IOException
	 */
	/*public ScriptNode(Node node, String str ) throws DetectorException, IOException {
		 this.node = node;
		 //it is easier to deal with Tag class becuase we know this node is a TAG node
		 this.tag = (Tag)node;
		 if (AttrTable.getInstance().getTable().contains(str)){
			 this.scriptType = ScriptType.ATTR;
			 this.attr = str;
		 }
		 else if (EventTable.getInstance().getTable().contains(str)){
			 this.scriptType = ScriptType.EVENT;
			 this.event = str;
		 }
		 else if (TagsTable.getInstance().getTable().contains(str))
			 this.scriptType = ScriptType.TAG;
		 else if (str=="uri_scheme")
			 this.scriptType = ScriptType.URI_SCHEME;
		 else
			 this.scriptType = ScriptType.UNKNOWN;
	 }*/

	/**
	 * 
	 */
	/*public boolean build (Vector<ScriptNode> scriptList) throws DetectorException, IOException{

		 boolean inList = false;

		 switch (scriptType){
		 case TAG:
			 handleScriptTag();
			 break;
		 case URI_SCHEME:
			 handleURIScheme();
			 break;
		 case ATTR :
			 handleAttr();
			 break;
		 case EVENT:
			 handleEvent();
			 break;
		 case UNKNOWN:
			 throw(new DetectorException("Unknown script type"));
		 }
		 if (this.origCode==null)
			 return false;
		 //compute hash values
		 this.hash = this.origCode.hashCode();
		 for (ScriptNode s : scriptList){
			 if (s.getHash()==this.hash){
				 inList=true;
				 break;
			 }
		 }
		 if (!inList){
			 if  ((this.scriptType==ScriptType.ATTR) && (!this.origCode.endsWith(".js")))
				 this.normCode = this.origCode;
			 else {
				 normalize();
			 }
		 }
		 else
			 this.normCode = this.origCode;

		 this.alternateHash = this.normCode.hashCode();

		 //find node path to root
		 StringBuffer sb = new StringBuffer();
		 while (node.getParent() != null) {
			 //in case parent tag has attributes - ignore them
			 int j = node.getParent().getText().indexOf(" ");
			 if (j > -1)
				 sb.append(node.getParent().getText().substring(0, j));
			 else
				 sb.append(node.getParent().getText());
			 sb.append("<--");	
			 node = node.getParent();
		 }
		 this.scriptPath = sb.toString();
		 return true;
	 }
	 */

	/**
	 * @override equals method
	 */ 
	public boolean equals(Object obj) {
		if (obj instanceof ScriptNode) {
			ScriptNode other = (ScriptNode) obj;
			if ( (this.hash == other.getHash())
					|| (this.alternateHash == other.getAlternateHash()) )
				return true;
		}
		return false;
	}


	public int hashCode() {
		// TODO Auto-generated method stub
		return super.hashCode();
	}

	public SourceType getSourceType() {
		return sourceType;
	}

	public String getSource() {
		return externalSourceAddress;
	}

	public int getHash() {
		return this.hash;
	}

	/**
	 * 
	 * @param output
	 * @param flag
	 * @param msg
	 * @throws IOException
	 */
	public void printScriptNode(BufferedWriter output, boolean flag,String msg) throws IOException{
		if (msg==null)
			output.write(newline + "Node Info: " + newline + "--------------------------------" + newline);
		else
			output.write(newline + msg + " Node Info: " + newline + "--------------------------------" + newline);
		output.write("Node Source:  " +  this.sourceType + newline);
		output.write("Node Script Type: " + this.scriptType + newline);
		output.write("Node code: " );
		switch (scriptType){
		case TAG:
			output.write("(script)" + newline);
			if (this.sourceType == SourceType.INLINE){
				if (flag==true){//print entire code block
					output.write(origCode + newline);
					output.write("Alternative script code: " + newline + normCode);
				}
				else{ //print first code line
					int j = origCode.trim().indexOf(';');
					if (j>-1)
						output.write(origCode.trim().substring(0, j));
					else 
						output.write(origCode.trim());
				}
			}
			else  // this is an external script
				if (flag==true) {// print entire code block
					output.write(this.origCode + newline);
					output.write("Alternative script code: " +newline + normCode);
				}
				else//print only source url
					output.write(externalSourceAddress);
			break;
		case ATTR:
			output.write("(" + attr + ")" + newline);
			output.write(attr +"=" +origCode + newline);
			if (flag == true)
				output.write("Alternative script code: " +newline  + attr +"=" + normCode);
			break;
		case EVENT:
			output.write("(" + event + ")" + newline);
			output.write(event + "=" + origCode + newline);
			if (flag == true)
				output.write("Alternative script code: " +newline  + event + "=" + normCode);
			break;
		case URI_SCHEME:
			output.write("(URI_Scheme)" + newline);
			output.write("javascript: " + origCode + newline);
			if (flag == true)
				output.write("Alternative script code: " +newline  + "javascript: " + normCode);
			break;
		}
		output.write(newline);
		output.write("Node Hash : " + this.hash + newline);
		output.write("Alternative Node Hash : " + this.alternateHash + newline);
	}

	public int getAlternateHash() {
		return alternateHash;
	}

	public ScriptType getScriptType() {
		return scriptType;
	}

	public void setScriptType(ScriptType scriptType) {
		this.scriptType = scriptType;
	}

	public String getAttr() {
		return attr;
	}

	public String getEvent() {
		return event;
	}

	public boolean isSkip() {
		return skip;
	}

	public void setSkip(boolean skip) {
		this.skip = skip;
	}

	public Tag getTag() {
		return tag;
	}



}

